﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Speech.Synthesis;

class KIDJ
{
    //Global variables
    public const string VERSION = "2.00";

    private readonly Random _rand = new Random();
    private readonly SpeechSynthesizer _synthezis = new SpeechSynthesizer();
    private readonly SpeechSynthesizer _speech = new SpeechSynthesizer();

    private bool _requestupdate;
    private bool _mastercommand;
    private bool _enableSpeech;

    //External files
    private const string STATICFILE = "datafiles\\brain.dat.enc";
    private const string TOPICFILE = "datafiles\\database.dat.enc";
    private const string SYNONYMSFILE = "datafiles\\synapse.dat.enc";
    private const string KNOWLEDGEFILE = "datafiles\\knowledge.dat.enc";
    private const string TEMP = ".TMP";

    //KIDJ brain
    private const byte BRAINSIZE = 255;
    private const byte SMALLBRAINSIZE = 255;
    private const byte CELLAMOUNT = 255;

    //Core memory stuff
    private readonly Brain _brain;
    private readonly ShortTermMemory _memory;

    static string[] topicindex = new string[BRAINSIZE];           //Whole topic database
    static byte[] topicsize = new byte[topicindex.Length];
    static string[,] synapsecells = new string[topicindex.Length, CELLAMOUNT];
    static sbyte[] topiccode = new sbyte[topicindex.Length];                        //What CURRENTOPIC must be to allow the topic to be discussed. -1 is always.

    static string[,] knowledgebrain = new string[topicindex.Length, SMALLBRAINSIZE]; //Knowledge database
    static sbyte[] knowledgesize = new sbyte[topicindex.Length];
    
    //Short term memory stuff
    public static sbyte CURRENTTOPIC = -1;                    //What are we talking about now?
    public static sbyte LASTTOPIC = -1;                       //What was the last thing we talked about?
    public static sbyte ANCIENTTOPIC = -1;                   //BAD! We should make a array of topics talked instead

    private double IQ;                                   //KIDJ's IQ
    const byte MEMORYSIZE = 3;
    private string[] lastsaid_kid = new string[MEMORYSIZE * 2];                                 //What is the last thing KIDJ said about a topic?
    private int _totallinesloaded;                     //Total number of lines loaded from all topics
    private bool _forcesay;

    public KIDJ()
    {
        _brain = new Brain();
        _memory = new ShortTermMemory();
    }

    //----------------------------------------------------------------------
    //This resets short memory
    public void ClearShortMemory()
    {
        _memory.Clear();
        Say("I am forgetting all my short term memory...");
        CURRENTTOPIC = 0;
        LASTTOPIC = 0;
        ANCIENTTOPIC = 0;
        Array.Clear(lastsaid_kid, 0 , lastsaid_kid.Length);
    }

    //----------------------------------------------------------------------
    //This releases all KID resources.
    public void Kill()
    {
        Console.ForegroundColor = ConsoleColor.Blue;
        Console.WriteLine("Goodbye!");
        _speech.Speak("Goodbye!");
        Log.Write("\nShutting down KID...", Log.LogLevel.Normal);
        _speech.Dispose();

        Log.Write("Terminating core program...", Log.LogLevel.System);
        Console.BackgroundColor = ConsoleColor.Yellow;
        Console.ForegroundColor = ConsoleColor.Black;
        Console.WriteLine("\n\n--PRESS ANY KEY TO TERMINATE PROGRAM--");
        Console.ReadKey();
        Log.CloseLog();

        Environment.Exit(Environment.ExitCode);
    }

    //----------------------------------------------------------------------
    //This loads the synonyms for a topic
    //This means words that also trigger the same topic
    private bool LoadSynapseLinks()
    {
        UInt16 topic = 0, synonym = 0;
        //Couple of error checks
        if (!DataFile.UnpackData(SYNONYMSFILE))
        {
            Log.Write("File does not exist: " + SYNONYMSFILE, Log.LogLevel.Error);
            return false;
        }
        
        //Nothing went wrong, so open that file!
        StreamReader readFile = new StreamReader(SYNONYMSFILE+TEMP);
        while (!readFile.EndOfStream)
        {
            //New topic found, renew the topic and synonom numbers
            switch (readFile.Peek())
            {
                case '@':
                    {
                        //Get & Set topic index value
                        topic = 0;
                        readFile.Read();                                //Skip the @ symbol

                        string topicread = readFile.ReadLine().ToUpper();
                
                        //Skip topics until we find one that fits
                        while (topicread != topicindex[topic])
                        {
                            topic++;
                            if (topic == topicindex.Length)
                            {
                                Log.Write("WARNING: Trying to load a synonym to nonexisting topic. (" + topicread + ")", Log.LogLevel.Warning);
                                topic = 0;
                                break;
                            }
                        }
                        synonym = 0;
                    }
                    break;

                case '#':
                    readFile.ReadLine();     //skip comments
                    break;

                default:
                    synapsecells[topic, synonym] = readFile.ReadLine();
                    synonym++;
                    if (synonym == CELLAMOUNT)     //File reads more information than we can store!
                    {
                        Log.Write("Warning! No more room for additional synonyms!", Log.LogLevel.Error);
                        Say("It seems my programmer created too few synapse cells. It should not be something critical.");
                        synonym = 0;
                    }
                    break;
            }
        }

        //Finish and clean it up!
        readFile.Close();
        File.Delete(SYNONYMSFILE+TEMP);
        return true;
    }




    //----------------------------------------------------------------------
    //This loads all knowledge known about a specific topic
    public bool LoadTopicKnowledge(string topictoload, int saveslot)
    {
        byte line = 0;
        bool topicfound = false;
        string topicread = "";

        //See if file exists
        if (!DataFile.UnpackData(KNOWLEDGEFILE))
        {
            Log.Write("File does not exist: " + KNOWLEDGEFILE, Log.LogLevel.Error);
            SayRandomError();
            return false;
        }

        //Nothing went wrong, so open that file!
        StreamReader readFile = new StreamReader(KNOWLEDGEFILE+TEMP);
        while (!readFile.EndOfStream && topictoload != topicread)
        {
            readFile.Read();
            topicread = readFile.ReadLine();
            if (topictoload == topicread) topicfound = true;
        }

        //Topic was found, now we load all relevant knowledge
        if (topicfound)
        {
            while (readFile.Peek() != '@' && !readFile.EndOfStream)
            {
                if (readFile.Peek() == '#') readFile.ReadLine();    //skip comments
                else
                {
                    topicread = readFile.ReadLine();
                    knowledgebrain[saveslot, line] = topicread;
                    line++;

                    //Error check
                    if (line == SMALLBRAINSIZE)
                    {
                        Log.Write("Array too small! -> knowledgebrain[OK, TOO_SMALL]", Log.LogLevel.Warning);
                        Say("I have more knowledge I wanted to load, but my brainsize was assigned too small.");
                        break;
                    }
                }
            }
                //Finished loading this topic
                knowledgesize[saveslot] = Convert.ToSByte(line);
                _totallinesloaded += Convert.ToByte(knowledgesize[saveslot]);
                IQ += Convert.ToDouble(knowledgesize[saveslot]) / 75;
        }

        //could not load anything about topic
        else knowledgesize[saveslot] = -1;

        //All done with this function
        readFile.Close();
        File.Delete(KNOWLEDGEFILE + TEMP);
        return true;
    }


    //----------------------------------------------------------------------
    //This function loads all replies and topics into memory
    public void LoadBrain()
    {
        bool noerrors = true;

        //If we are reloading the brain, say lot of fancy stuff...
        if (_requestupdate)
        {
            Say("Give me a second... I am loading my brain.");
            ClearShortMemory();
            _brain.Clear();
            Array.Clear(topicindex, 0, topicindex.Length);
            Array.Clear(topicsize, 0, topicsize.Length);
            Array.Clear(synapsecells, 0, synapsecells.Length);
            Array.Clear(knowledgebrain, 0, knowledgebrain.Length);
            Array.Clear(knowledgesize, 0, knowledgesize.Length);
            IQ = 0;
            _totallinesloaded = 0;
        }
        

        //Load voices
        Console.WriteLine("Checking for installed voices...");
        if (_synthezis.GetInstalledVoices().Count > 0)
        {
            Console.WriteLine("Number of voices found: " + _synthezis.GetInstalledVoices().Count + ":");
            int counter = 0;

            //List the avalible voices
            foreach (InstalledVoice voice in _synthezis.GetInstalledVoices())
            {
                Console.WriteLine(counter + ": " + voice.VoiceInfo.Name + "(" + voice.VoiceInfo.Gender + ") from " + voice.VoiceInfo.Culture + ".");
            }
            Console.WriteLine(++counter + ": Do not use a voice. Text only, please.");
            Console.WriteLine();
            Console.WriteLine("Which voice should be used? ");

            String input = Console.ReadLine();
            
            //Parse value
            int value;
            if (!int.TryParse(input, out value))
            {
                Log.Write("ERROR: Invalid voice selected.", Log.LogLevel.Error); 
            }


            //Read the voice they want (or disable if that's what they said)
            if (value == counter)
            {
                Console.WriteLine("Text only mode selected.");
                _enableSpeech = false;
            }
            else
            {
                _speech.SelectVoice((_synthezis.GetInstalledVoices()[value].VoiceInfo.Name));
                Log.Write("You selected " + (_synthezis.GetInstalledVoices()[value].VoiceInfo.Name), Log.LogLevel.Warning);
                _enableSpeech = true;
            }
        }
        else
        {
            Console.WriteLine("None voices found, text mode only activated.");
            _enableSpeech = false;
        }
        

        if (_requestupdate)
        {
            Say("Initializing SMART systems...");
            Say("Initializing advanced grammar analysis...");
        }
        
        //Load data
        if (!_brain.LoadBrainCore(STATICFILE)) noerrors = false;
        if (_requestupdate) Say("Initializing Artifical Intelligence guidance system...");
        if (!_brain.LoadAllTopics(TOPICFILE)) noerrors = false;
        if (_requestupdate) Say("Initializing intelligent linked index system...");
        if (!LoadSynapseLinks()) noerrors = false;

        //Something went wrong
        if (!noerrors) SayRandomError();

        //Enable debug mode
        if (File.Exists("debug.exe"))
        {
            Log.Write("Learning mode is ENABLED.", Log.LogLevel.Error);
            DisplayBrain();
        }

        //Finish it up
        if (_requestupdate)
        {
            Say("Last access time was: " + Convert.ToString(File.GetLastAccessTime("read test.exe")));
            Say("I was born: " + Convert.ToString(File.GetCreationTime("read test.exe")));
            Say("It is done! I am \"smart\" now.");
            _requestupdate = false;
        }
    }

    //----------------------------------------------------------------------
    //This makes KIDJ spit out predefined, hardcoded error messages
    private void SayRandomError()
    {
        string theerror;

        switch(_rand.Next(0, 4))
        {
            case 0: theerror = "FATAL ERROR: It seems my brain damage is messing things up..."; break;
            case 1: theerror = "HELP! Something is wrong with my brain! There seems some circuts are missing."; break;
            case 2: theerror = "I just had a tingly feeling that something did not go right..."; break;
            case 3: theerror = "I've detected an error in my memory database. Let's hope it's nothing critical."; break;
            case 4: theerror = "There seems an error just occured. Was it bad?"; break;
            default: theerror = "I detect even that my error messaging system has an error!"; break;
        }

        Say(theerror);
    }

    //----------------------------------------------------------------------
    //Makes blue text pop up and KIDJ voice talk
    public void Say(string whattosay)
    {
        byte i = 0;
   
        if (whattosay == null)
        {
            Log.Write("Kid tries to say something that does not exist!", Log.LogLevel.Error);
            SayRandomError();
            return;
        }

        //We have our response now
        _forcesay = false;

        //Replace the appropiate escape functions
        whattosay = ReplaceEscapes(whattosay);

        //Do not repeat yourself
        if (_memory.Mode != ShortTermMemory.TalkMode.Greeting)
        {
            while (i < lastsaid_kid.Length)
            {
                if (lastsaid_kid[i] == whattosay)
                {
                    Say(_brain.GetCoreLine(Brain.CoreTopics.NewTopicPlease));
                    return;
                }
                i++;
            }
        }

        //Update what KIDJ has said in the last while
        if (_memory.Mode != ShortTermMemory.TalkMode.Greeting || !_brain.CheckTopic(Brain.CoreTopics.AlreadySaid, whattosay) || !_brain.CheckTopic(Brain.CoreTopics.NewTopicPlease, whattosay) || !_brain.CheckTopic(Brain.CoreTopics.SayingBye, whattosay) || !_brain.CheckTopic(Brain.CoreTopics.UnknownTopic, whattosay))
        {
            i = 1;
            while (i != lastsaid_kid.Length)
            {
                lastsaid_kid[lastsaid_kid.Length - i] = lastsaid_kid[lastsaid_kid.Length - i - 1];
                i++;
            }
            lastsaid_kid[0] = whattosay;
        }

        //Now finally say the stuff!
        Log.Write(whattosay, Log.LogLevel.Verbatim);
        
        if(_enableSpeech)
            _speech.SpeakAsync(whattosay);
    }

    //----------------------------------------------------------------------
    //Makes KIDJ stop speaking
    public void ShutUp()
    {
        _speech.SpeakAsyncCancelAll();
    }

    //----------------------------------------------------------------------
    //Returns true if KIDJ is talking
    public bool IsTalking()
    {
        return _speech.State == SynthesizerState.Speaking;
    }

    //----------------------------------------------------------------------
    //This checks if the specified topic is in KIDJ's brain in the following order:
    //First it checks if it is a "continue speaking" word. If not, continue.
    //I:   Continue talking about current topic? ("go on, continue, tell me more, etc.")
    //II:  A subtopic?
    //III: Synonym database
    //IV:  Searches the whole sentence for a fit
    public bool ConfirmTopic(string whichtopic)
    {
        byte index = 0;
        bool topicfound = false;

        //Do not try to confirm a master command
        if (_mastercommand) return _mastercommand = false;

        //There is no topic for nothing
        if (whichtopic == null) return false;
        whichtopic = whichtopic.ToUpper();

        //The player is repeating himself or herself?
        if (_memory.HumanIsRepeating(whichtopic))
        {
            Say(_brain.GetCoreLine(Brain.CoreTopics.AlreadySaid));
            return false;
        }

        //I: It is a "continue speaking" word
        if (CURRENTTOPIC != -1 && (_brain.CheckTopic(Brain.CoreTopics.Continue, whichtopic) || _brain.CheckTopic(Brain.CoreTopics.Positive, whichtopic))) 
        {
            if (knowledgesize[CURRENTTOPIC] < 1 && LASTTOPIC != -1 && knowledgesize[LASTTOPIC] > 0)
            {
                CURRENTTOPIC = LASTTOPIC;
            }
            else if (knowledgesize[CURRENTTOPIC] < 1)
            {
                //SayBrainCoreLine("NOTUNDERSTAND");
                //SayBrainCoreLine("TOPICSTART");    //DEBUG: Which is best to keep?
                return false;
            }
            return true;
        }

        //III: search all synonyms for a match
        if (!topicfound)
        {
            byte bestmatchIndex = BRAINSIZE;
            byte bestmatchSynonym = CELLAMOUNT;
            byte synonym = 0;
            index = 0;
            while (index != BRAINSIZE)
            {
                if (synapsecells[index, synonym] != null && whichtopic.Contains(synapsecells[index, synonym]))
                {
                    if (topiccode[index] == -1 || topiccode[index] == CURRENTTOPIC) //Only search trough subtopics of the current topic
                    {
                        //We found our first match
                        if (bestmatchIndex == topicindex.Length || synapsecells[bestmatchIndex, bestmatchSynonym] == null)
                        {
                            bestmatchIndex = index;
                            bestmatchSynonym = synonym;
                            topicfound = true;
                        }
                        //Always keep the biggest matching synonym
                        else if (synapsecells[index, synonym].Length >= synapsecells[bestmatchIndex, bestmatchSynonym].Length)
                        {
                            bestmatchIndex = index;
                            bestmatchSynonym = index;
                        }
                    }
                }
                synonym++;

                //We reached the end of this array, start with the next one
                if (synapsecells[index, synonym] == null)
                {
                    synonym = 0;
                    index++;
                }
            }
            //This is the best match we found so far
            if (topicfound) index = bestmatchIndex;
        }

        //IV: If no match found, analyse sentence for a word match (less likely that it's what the player wants)
        if (!topicfound)
        {
            index = 0;
            while (index < BRAINSIZE)        //Search each topic
            {
                if (topiccode[index] == -1 || topiccode[index] == CURRENTTOPIC)         //Only search trough subtopics of the current topic
                {
                    if (whichtopic.Contains(topicindex[index]))
                    {
                        topicfound = true;
                        break;
                    }
                }
                index++;
                if (index == BRAINSIZE || topicindex[index] == null) break;    //We hit the last topic without luck
            }
        }

        //BAD! We have no clue what the player is trying to say
        if (!topicfound)
        {
            Say(_brain.GetCoreLine(Brain.CoreTopics.NotUnderstand));
            Say(_brain.GetCoreLine(Brain.CoreTopics.UnknownTopic));
            return false;
        }

        //A new topic was found and give an immidiate response
        if (Convert.ToSByte(index) != LASTTOPIC)
        {
            ANCIENTTOPIC = LASTTOPIC;
            LASTTOPIC = CURRENTTOPIC;
            CURRENTTOPIC = Convert.ToSByte(index);
            if (LASTTOPIC != CURRENTTOPIC || knowledgebrain[index, 0] == null)
                Say(_brain.GetTopicRamble(_memory.CurrentTopic));
        }

        return true;
    }

    //----------------------------------------------------------------------
    //This makes kid talk about the current topic
    private void TalkAboutTopic()
    {
        //Nothing to talk about here...
        if (knowledgesize[CURRENTTOPIC] == -1)
        {
            if(_forcesay) Say(_brain.GetCoreLine(Brain.CoreTopics.NewTopicPlease));
            return;   
        }

        byte randomline = Convert.ToByte(_rand.Next(0, knowledgesize[CURRENTTOPIC]));
        Say(knowledgebrain[CURRENTTOPIC, randomline]);
    }

    //----------------------------------------------------------------------
    //This makes KIDJ say all topics known
    private void DisplayAllTopics()
    {
        Say("These are the topics I currently have loaded into memory:");
        byte i = 0;
        while (topicindex[i] != null)
        {
            Say(topicindex[i]);
            i++;
        }
    }

    //----------------------------------------------------------------------
    //This makes KIDJ show his brain contents
    private void DisplayBrain()
    {
        int y;

        Log.Write("\nKIDJ CURRENT BRAIN CONTENTS:", Log.LogLevel.Warning);
        Log.Write("----------------------------", Log.LogLevel.Warning);
        Log.Write("My current IQ is: " + Math.Round(_brain.GetIQ(), 2), Log.LogLevel.Warning);
        Log.Write("Topics loaded: " + _brain.GetTopicsLoaded() + " of a maximum "+ topicindex.Length + " topics.", Log.LogLevel.Warning);
        Log.Write("Lines of information loaded: " + _totallinesloaded, Log.LogLevel.Warning);
        

        Log.Write("\nPress any key to display core brain functions...", Log.LogLevel.Error);

        //First show core brain memories
        foreach (KeyValuePair<Brain.CoreTopics, List<string>> pair in _brain.GetBigBrain())
        {
            y = 0;
            Log.Write("\nMEMORY TYPE: " + pair.Key, Log.LogLevel.Warning);
            foreach (string line in pair.Value)
            {
                Log.Write("Row " + y + ": " + line, Log.LogLevel.Normal);
                y++;
                
            }
        }


        //Secondly show all topics known
        Log.Write("\nPress any key to display topics known...", Log.LogLevel.Error);
        _brain.PrintAllTopics();

        //Last show what we know about each specific topic
        Log.Write("\nPress any key to display what I know about each topic...", Log.LogLevel.Error);
        int i = 0;
        while (i != BRAINSIZE)
        {
            if (topicindex[i] == null) break;     //No need to show empty ones
            Log.Write("\nKNOWLEDGE OF TOPIC: " + topicindex[i], Log.LogLevel.Warning);
            y = 0;
            while (y != knowledgesize[i])
            {
                if (knowledgesize[i] == -1) break;      //Stop here, there isn't anything here anyways
                Log.Write("Line " + y + ": " + knowledgebrain[i, y], Log.LogLevel.Normal);
                y++;
            }
            i++;
        }

        Log.Write("\nPress any key to display all synonyms loaded...", Log.LogLevel.Error);

        //Memory lapse, also show all known synonyms
        i = 0;
        while (i != topicindex.Length)
        {
            if (topicindex[i] == null) break;     //No need to show empty ones
            Log.Write("\nSYNONYMS FOR: " + topicindex[i], Log.LogLevel.Warning);
            y = 0;
            while (y != CELLAMOUNT)
            {
                if (synapsecells[i, y] == null) break;     //No need to show empty ones
                Log.Write("Synonym " + y + ": " + synapsecells[i, y], Log.LogLevel.Normal);
                y++;
            }
            i++;
        }
    }

    //----------------------------------------------------------------------
    //This function replaces escape functions (the little %*) with appropiate 
    //strings and returns it
    /*LIST OF CURRENT ESCAPES:
         * %n - playername
         * %g - last thing the player said
         * %i - KIDJ current IQ
         * %k - KIDJ's name
         * %c - current topic that is being discussed
         * %a - computer network name
         * %r - a random topic
         * %t - number of CPU's in this computer
         * %l - the name the user is logged on
         * %x - last topic that was discussed
         * %z - last topic before last topic that was discussed
         * %m - the time
         * %j - the date
         * %t - number of topicsloaded 
    */
    public string ReplaceEscapes(string result)
    {
        if (result == null)
        {
            Log.Write("BAD! Trying to replace escapes in a null string.", Log.LogLevel.Error);
            return null;
        }

        int index;
        while ((index = result.IndexOf('%')) != -1)
        {
            switch (result[index+1])
            {
                //%n - playername
                case 'n':
                    result = result.Replace("%n", _memory.GetHumanName());
                    break;

                //%g - last thing the player said
                case 'g':
                    result = result.Replace("%g", _memory.GetLastHumanString());
                    break;

                //%i - KIDJ current IQ
                case 'i':
                    result = result.Replace("%i", Convert.ToString(Math.Round(IQ, 2)));
                    break;

                //%k - KIDJ's name
                case 'k':
                    result = result.Replace("%k", _memory.GetMyName());
                    break;

                //%c - current topic that is being discussed
                case 'c':
                    if (CURRENTTOPIC > -1 && topicindex[CURRENTTOPIC] != null) result = result.Replace("%c", topicindex[CURRENTTOPIC].ToLower());
                    else result = result.Replace("%c", "the first thing that comes to your mind");
                    break;

                //%a - computer network name
                case 'a':
                    result = result.Replace("%a", Environment.MachineName);
                    break;

                //%r - a random topic
                case 'r':
                    _memory.CurrentTopic = _brain.GetRandomTopic(_memory);
                    result = result.Replace("%r", _memory.CurrentTopic);
                    break;

                //%t - number of CPU's in this computer
                case 'p':
                    result = result.Replace("%p", Convert.ToString(Environment.ProcessorCount));
                    break;

                //%d - the directory KIDJ is running in
                case 'd':
                    result = result.Replace("%d", Environment.CurrentDirectory);
                    break;

                //%l - the name the user is logged on
                case 'l':
                    result = result.Replace("%l", Environment.UserName);
                    break;

                //%x - last topic that was discussed
                case 'x':
                    if (LASTTOPIC > -1 && topicindex[LASTTOPIC] != null) result = result.Replace("%x", topicindex[LASTTOPIC].ToLower());
                    else result = result.Replace("%x", "something completely else");
                    break;

                //%z - the topic before the last topic that was discussed
                case 'z':
                    if (ANCIENTTOPIC > -1 && topicindex[ANCIENTTOPIC] != null) result = result.Replace("%x", topicindex[LASTTOPIC].ToLower());
                    else result = result.Replace("%z", "something we have talked about before");
                    break;

                //%m - the time
                case 'm':
                    result = result.Replace("%m", DateTime.Now.TimeOfDay.Hours + ":" + DateTime.Now.TimeOfDay.Minutes + ":" + DateTime.Now.TimeOfDay.Seconds);
                    break;

                //%j - the date
                case 'j':
                    result = result.Replace("%j", DateTime.Now.Date.ToShortDateString());
                    break;

                //%t - numbers of topics loaded
                case 't':
                    result = result.Replace("%t", Convert.ToString(_brain.GetTopicsLoaded()));
                    break;

                //%v - program version
                case 'v':
                    result = result.Replace("%v", VERSION);
                    break;
               
                //Invalid escape
                default:
                    Log.Write("Unknown escape parsed: " + result[index+1], Log.LogLevel.Warning);
                    result = result.Replace("%" + result[index + 1], "[UNKNOWN_ESCAPE]");
                    break;
            }
        }

        return result;
    }

    //----------------------------------------------------------------------
    //This function initiates the greeting process
    private void Greet()
    {
        bool refuse = false;
        string tempName = String.Empty;

        Say(_brain.GetCoreLine(Brain.CoreTopics.Greet));
        Say(_brain.GetCoreLine(Brain.CoreTopics.WhoAreYou));

        //Now try to find out the name
        while(true)
        {
            if (tempName == String.Empty) tempName = GetInput();
            
            //This removes silly stuff
            tempName = tempName.ToUpper();
            tempName = tempName.Replace("MY NAME IS ", null);
            tempName = tempName.Replace(" IS MY NAME", null);
            tempName = tempName.Replace("I AM NAMED ", null);
            tempName = tempName.Replace("YES I AM ", null);
            tempName = tempName.Replace("I AM ", null);
            tempName = tempName.Replace("I HAVE THE NAME ", null);
            tempName = tempName.Replace("I GO UNDER THE NAME ", null);
            tempName = tempName.Replace("I TOLD YOU MY NAME IS ", null);
            tempName = tempName.Replace("I TOLD YOU IT IS", null);
            tempName = tempName.ToLower();

            //Confirm the name...
            Say("So your name is \"" + tempName + "\"?");

            string input = GetInput();
            input = input.Replace("I JUST TOLD YOU SO", "YES");
            input = input.Replace("WHAT DO YOU THINK", "YES");

            if (_brain.CheckTopic(Brain.CoreTopics.Positive, input))
            {
                _memory.SetHumanName(tempName);
                break;
            }

            if (_brain.CheckTopic(Brain.CoreTopics.Negative, input))
            {
                Say("Can you tell me what your name really is then?");
                input = GetInput();
                if (_brain.CheckTopic(Brain.CoreTopics.Negative, input))
                {
                    //Either pick last name or the logon name
                    if(tempName != String.Empty && _rand.Next(0, 2) > 0)
                        Say("Shall I simply call you " + tempName + " instead?");
                    else Say("Could I call you %l?");

                    input = GetInput();

                    if (_brain.CheckTopic(Brain.CoreTopics.Positive, input))
                    {
                        Say("Okay, I will use your nickname \"" + tempName + "\".");
                        _memory.SetHumanName(tempName);
                        break;
                    }

                    if (_brain.CheckTopic(Brain.CoreTopics.Negative, input))
                    {
                        refuse = true;
                        _memory.SetHumanName("\"Mr SecretName\"");  //TODO: add gibberish name?
                        break;
                    }

                    Say(_brain.GetCoreLine(Brain.CoreTopics.NotUnderstand));
                    Say("Let's begin again. What is your name?");
                    tempName = GetInput();
                }
                else if (_brain.CheckTopic(Brain.CoreTopics.Positive, input))
                {
                    Say(_brain.GetCoreLine(Brain.CoreTopics.WhoAreYou));
                    tempName = GetInput();
                }
                else
                {
                    //Assume the answer was a name if not POSITIVE or NEGATIVE
                    tempName = input;
                }
            }
            else Say(_brain.GetCoreLine(Brain.CoreTopics.NotUnderstand));
        }

        Say(refuse ? "Okay! Be that way!" : "Hello %n! Nice to meet you!");
        _memory.Mode = ShortTermMemory.TalkMode.Talking;
    }

    public void Goodbye()
    {
        Say(_brain.GetCoreLine(Brain.CoreTopics.Goodbye));
        if (_brain.CheckTopic(Brain.CoreTopics.Positive, GetInput()))
            Kill();
        else Say("I take that as a no.");
    }

    public void MainLoop()
    {
        //Say hello world
        Log.InitializeLogging();
        Log.Write("Starting KID JUNIOR " + VERSION + " by Johan Jansen...", Log.LogLevel.Normal);

        //Try to encode any new uncoded datafiles
        foreach (string dataFile in Directory.GetFiles("datafiles", "*.dat"))
        {
            DataFile.EncodeFile(dataFile);
        }

        //Load stuff into memory
        LoadBrain();
        Log.Write("PRESS ALT+ENTER FOR FULLSCREEN\n", Log.LogLevel.Warning);

        //And we are off!
        while (true)
        {
            switch (_memory.Mode)
            {
                case ShortTermMemory.TalkMode.Greeting:
                    Greet();
                    Say(_brain.GetCoreLine(Brain.CoreTopics.TopicStart));
                    break;

                case ShortTermMemory.TalkMode.Talking:
                    if (ConfirmTopic(GetInput().ToUpper())) TalkAboutTopic();
                    else Say(_brain.GetCoreLine(Brain.CoreTopics.TopicStart));
                    break;

                case ShortTermMemory.TalkMode.Goodbye:
                    Goodbye();
                    break;

                case ShortTermMemory.TalkMode.MasterCommand:
                    break;

                default:
                    SayRandomError();
                    _memory.Mode = ShortTermMemory.TalkMode.Talking;
                    break;
            }


        }

        //Release resources and exit
        Kill();
    }

    //----------------------------------------------------------------------
    //This is the main program loop for KIDJ
    private static void Main()
    {
        new KIDJ().MainLoop();
    }

    //----------------------------------------------------------------------
    //This reads the user input, ignoring if it is null and returns it
    //If it is EXIT, it will begin shutdown progress... It also filters out
    //silly characters we don't want
    public string GetInput()
    {
        string input = null;

        while (String.IsNullOrEmpty(input))
        {
            input = Console.ReadLine();
            if(String.IsNullOrEmpty(input)) continue;

            if (IsTalking()) ShutUp();

            //Special master commands
            if (input.StartsWith("#"))
            {
                input = input.ToUpper();

                switch (input)
                {
                    case "#RELOAD BRAIN":
                        _requestupdate = true;
                        LoadBrain();
                        break;

                    case "#HELP":
                        Say("Sentences that start with a # designates your input as a master command.\n #CLEAR MEMORY - Clears all short term stuff loaded into memory.\n #SAY FOLLOWING - Makes the program say your next input out loud.\n #KILL KID - Instantly exits the program, not releasing resources.\n #RELOAD BRAIN - Clears all resources and reloads all datafiles.\n #DISPLAY BRAIN - Shows everything that has been loaded into memory from datafiles.\n #HELP - Makes KIDJ tell you about Master Commands.\n #CLEAR - Clears the console screen.\n#LIST - Makes KIDJ list all the loaded topics\n");
                        break;

                    case "#CLEAR MEMORY":
                        ClearShortMemory();
                        Greet();
                        break;

                    case "#SAY FOLLOWING":
                        Say(GetInput());
                        break;

                    case "#KILL KID":
                        Environment.Exit(-1);
                        break;

                    case "#DISPLAY BRAIN":
                        DisplayBrain();
                        break;

                    case "#LIST":
                        DisplayAllTopics();
                        break;

                    case "#CLEAR":
                        Console.Clear();
                        Log.Write("Console has been cleared.", Log.LogLevel.Normal);
                        break;
                    
                    default:
                        Log.Write("That is not a valid Master Command.", Log.LogLevel.Normal);
                        break;
                }
                _mastercommand = true;
                return input;
            }
        }

        //Filter out  the input for silly stuff we dont need
        char[] whattotrim = { '^', '¨', '#', '.', '.', ',', '!', '?', '"', '(', ')', ';', '\'' };
        input = input.TrimStart(whattotrim);
        input = input.TrimEnd(whattotrim);

        //Is player trying to quit?
        if (_brain.CheckTopic(Brain.CoreTopics.Goodbye, input)) Goodbye();

        //Remember the input for a while
        _memory.UpdateMemory(input);

        //Log it
        Log.Write(input, Log.LogLevel.Normal, false);

        //All done
        return input;
    }
}
